﻿
namespace EasyNet.FastDFS
{
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Configuration;
    using System.IO;

    public class StorageClient
    {
        private static Dictionary<string, StorageClient> instances = new Dictionary<string, StorageClient>();

        public static void Setup(string name, string[] servers)
        {
            if (instances.ContainsKey(name))
            {
                throw new ConfigurationErrorsException("Trying to configure StorageClient instance \"" + name + "\" twice.");
            }
            instances[name] = new StorageClient(name, servers);
        }

        public static bool Exists(string name)
        {
            return instances.ContainsKey(name);
        }

        private static StorageClient defaultInstance = null;

        public static StorageClient GetInstance()
        {
            return defaultInstance ?? (defaultInstance = GetInstance("Default"));
        }

        public static StorageClient GetInstance(string name)
        {
            StorageClient c;

            if (instances.TryGetValue(name, out c))
            {
                return c;
            }
            else
            {
                var config = ConfigurationManager.GetSection("FDFSStorages") as NameValueCollection;

                if (config != null && !string.IsNullOrEmpty(config.Get(name)))
                {
                    Setup(name, config.Get(name).Split(new char[] { ',' }));
                    return GetInstance(name);
                }

                throw new ConfigurationErrorsException("Unable to find StorageClient instance \"" + name + "\".");
            }
        }

        private readonly ServerPool serverPool;

        public int SendReceiveTimeout { get { return serverPool.SendReceiveTimeout; } set { serverPool.SendReceiveTimeout = value; } }

        public int ConnectTimeout { get { return serverPool.ConnectTimeout; } set { serverPool.ConnectTimeout = value; } }

        public uint MinPoolSize
        {
            get { return serverPool.MinPoolSize; }
            set
            {
                if (value > MaxPoolSize) { throw new ConfigurationErrorsException("MinPoolSize (" + value + ") may not be larger than the MaxPoolSize (" + MaxPoolSize + ")."); }
                serverPool.MinPoolSize = value;
            }
        }

        public uint MaxPoolSize
        {
            get { return serverPool.MaxPoolSize; }
            set
            {
                if (value < MinPoolSize) { throw new ConfigurationErrorsException("MaxPoolSize (" + value + ") may not be smaller than the MinPoolSize (" + MinPoolSize + ")."); }
                serverPool.MaxPoolSize = value;
            }
        }

        public TimeSpan SocketRecycleAge { get { return serverPool.SocketRecycleAge; } set { serverPool.SocketRecycleAge = value; } }

        private uint compressionThreshold = 1024 * 128;

        public uint CompressionThreshold { get { return compressionThreshold; } set { compressionThreshold = value; } }

        private StorageClient(string name, string[] hosts)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ConfigurationErrorsException("Name of StorageClient instance cannot be empty.");
            }
            if (hosts == null || hosts.Length == 0)
            {
                throw new ConfigurationErrorsException("Cannot configure StorageClient with empty list of hosts.");
            }
            serverPool = new ServerPool(hosts);
        }


        #region 上传文件

        /// <summary>
        /// 上传本地文件
        /// </summary>
        /// <param name="storageServer"></param>
        /// <param name="localFilename"></param>
        /// <param name="fileExtName"></param>
        /// <returns></returns>
        public string[] Upload(StorageServer storageServer, string localFilename, string fileExtName)
        {
            return Upload(storageServer, null, localFilename, fileExtName);
        }

        /// <summary>
        /// 上传本地文件
        /// </summary>
        /// <param name="storageServer"></param>
        /// <param name="groupName"></param>
        /// <param name="local_filename"></param>
        /// <param name="file_ext_name"></param>
        /// <returns></returns>
        string[] Upload(StorageServer storageServer, string groupName, string local_filename, string file_ext_name)
        {
            return Upload(storageServer, Protocol.STORAGE_PROTO_CMD_UPLOAD_FILE, groupName, local_filename, file_ext_name);
        }

        /// <summary>
        /// 上传本地文件
        /// </summary>
        /// <param name="storageServer"></param>
        /// <param name="cmd"></param>
        /// <param name="groupName"></param>
        /// <param name="localFilename"></param>
        /// <param name="fileExtName"></param>
        /// <returns></returns>
        string[] Upload(StorageServer storageServer, byte cmd, string groupName, string localFilename, string fileExtName)
        {
            var f = new FileInfo(localFilename);

            using (var fis = f.OpenRead())
            {
                return Upload(storageServer, cmd, groupName, null, null, string.IsNullOrEmpty(fileExtName) ? f.Extension.Substring(1) : fileExtName, f.Length, new UploadStream(fis, f.Length));
            }
        }

        public string[] Upload(StorageServer storageServer, byte[] file_buff, int offset, int length, string file_ext_name)
        {

            return Upload(storageServer, null, file_buff, offset, length, file_ext_name);
        }

        public string[] Upload(StorageServer storageServer, string group_name, byte[] file_buff, int offset, int length,
           string file_ext_name)
        {
            return Upload(storageServer, Protocol.STORAGE_PROTO_CMD_UPLOAD_FILE, group_name, null, null, file_ext_name,
                   length, new UploadBuffer(file_buff, offset, length));
        }

        public string[] Upload(StorageServer storageServer, byte[] file_buff, string file_ext_name)
        {
            return Upload(storageServer, null, file_buff, 0, file_buff.Length, file_ext_name);
        }

        public string[] Upload(StorageServer storageServer, string group_name, byte[] file_buff, string file_ext_name)
        {
            return Upload(storageServer, Protocol.STORAGE_PROTO_CMD_UPLOAD_FILE, group_name, null, null, file_ext_name,
                   file_buff.Length, new UploadBuffer(file_buff, 0, file_buff.Length));
        }

        public string[] Upload(StorageServer storageServer, string group_name, long file_size, IUploadCallback callback, string file_ext_name)
        {

            return Upload(storageServer, Protocol.STORAGE_PROTO_CMD_UPLOAD_FILE, group_name, null, null, file_ext_name, file_size, callback);
        }

        public string[] Upload(StorageServer storageServer, string group_name, string master_filename, string prefix_name, string local_filename, string file_ext_name)
        {
            if (string.IsNullOrEmpty(group_name) || string.IsNullOrEmpty(master_filename) || string.IsNullOrEmpty(prefix_name))
            {
                throw new ArgumentNullException();
            }

            var f = new FileInfo(local_filename);

            using (var fis = f.OpenRead())
            {
                return Upload(storageServer, Protocol.STORAGE_PROTO_CMD_UPLOAD_SLAVE_FILE, group_name, master_filename, prefix_name,
                    string.IsNullOrEmpty(file_ext_name) ? local_filename.Substring(1) : file_ext_name, f.Length, new UploadStream(fis, f.Length));
            }
        }

        public string[] Upload(StorageServer storageServer, string group_name, string master_filename, string prefix_name,
           byte[] file_buff, string file_ext_name)
        {
            if (string.IsNullOrEmpty(group_name) || string.IsNullOrEmpty(master_filename) || string.IsNullOrEmpty(prefix_name))
            {
                throw new ArgumentNullException();
            }

            return Upload(storageServer, Protocol.STORAGE_PROTO_CMD_UPLOAD_SLAVE_FILE, group_name, master_filename, prefix_name,
                                       file_ext_name, file_buff.Length, new UploadBuffer(file_buff, 0, file_buff.Length));
        }

        public string[] Upload(StorageServer storageServer, string group_name, string master_filename, string prefix_name,
           byte[] file_buff, int offset, int length, string file_ext_name)
        {
            if (string.IsNullOrEmpty(group_name) || string.IsNullOrEmpty(master_filename) || string.IsNullOrEmpty(prefix_name))
            {
                throw new ArgumentNullException();
            }

            return Upload(storageServer, Protocol.STORAGE_PROTO_CMD_UPLOAD_SLAVE_FILE, group_name, master_filename, prefix_name, file_ext_name, length, new UploadBuffer(file_buff, offset, length));
        }

        public string[] Upload(StorageServer storageServer, string group_name, string master_filename,
           string prefix_name, long file_size, IUploadCallback callback,
           string file_ext_name)
        {
            return Upload(storageServer, Protocol.STORAGE_PROTO_CMD_UPLOAD_SLAVE_FILE, group_name, master_filename, prefix_name, file_ext_name, file_size, callback);
        }

        public string[] upload_appender_file(StorageServer storageServer, string local_filename, string file_ext_name)
        {
            return this.upload_appender_file(storageServer, null, local_filename, file_ext_name);
        }

        protected string[] upload_appender_file(StorageServer storageServer, string group_name, string local_filename, string file_ext_name)
        {
            return Upload(storageServer, Protocol.STORAGE_PROTO_CMD_UPLOAD_APPENDER_FILE, group_name, local_filename, file_ext_name);
        }

        public string[] upload_appender_file(StorageServer storageServer, byte[] file_buff, int offset, int length, string file_ext_name)
        {
            return upload_appender_file(storageServer, null, file_buff, offset, length, file_ext_name);
        }

        public string[] upload_appender_file(StorageServer storageServer, string group_name, byte[] file_buff, int offset, int length, string file_ext_name)
        {
            return Upload(storageServer, Protocol.STORAGE_PROTO_CMD_UPLOAD_APPENDER_FILE, group_name, null, null, file_ext_name, length, new UploadBuffer(file_buff, offset, length));
        }

        public string[] upload_appender_file(StorageServer storageServer, byte[] file_buff, string file_ext_name)
        {
            return this.upload_appender_file(storageServer, null, file_buff, 0, file_buff.Length, file_ext_name);
        }

        public string[] upload_appender_file(StorageServer storageServer, string group_name, byte[] file_buff, string file_ext_name)
        {
            return Upload(storageServer, Protocol.STORAGE_PROTO_CMD_UPLOAD_APPENDER_FILE, group_name, null, null, file_ext_name, file_buff.Length, new UploadBuffer(file_buff, 0, file_buff.Length));
        }

        public string[] upload_appender_file(StorageServer storageServer, string group_name, long file_size, IUploadCallback callback, string file_ext_name)
        {
            return Upload(storageServer, Protocol.STORAGE_PROTO_CMD_UPLOAD_APPENDER_FILE, group_name, null, null,
               file_ext_name, file_size, callback);
        }

        public int Append(StorageServer storageServer, string groupName, string appendFilename, string localFilename)
        {
            var f = new FileInfo(localFilename);

            using (var fis = f.OpenRead())
            {
                return Append(storageServer, groupName, appendFilename, f.Length, new UploadStream(fis, f.Length));
            }
        }

        public int Append(StorageServer storageServer, string groupName, string appendFilename, byte[] fileBuffer)
        {
            return this.Append(storageServer, groupName, appendFilename, fileBuffer.Length, new UploadBuffer(fileBuffer, 0, fileBuffer.Length));
        }

        public int Append(StorageServer storageServer, string groupName, string appendFilename, byte[] fileBuffer, int offset, int length)
        {
            return Append(storageServer, groupName, appendFilename, length, new UploadBuffer(fileBuffer, offset, length));
        }

        public int Append(StorageServer storageServer, string groupName, string appendFilename, long fileSize, IUploadCallback callback)
        {
            return serverPool.Execute<int>(serverPool.GetSocketPool(string.Format("{0}:{1}", storageServer.IP, storageServer.Port)), 0, delegate(PooledSocket storageSocket)
            {
                byte[] header;
                byte[] hexLenBytes;
                byte[] appenderFilenameBytes;
                int offset;
                long body_len;

                if (string.IsNullOrEmpty(groupName) || string.IsNullOrEmpty(appendFilename))
                {
                    return Protocol.ERR_NO_EINVAL;
                }


                appenderFilenameBytes = Protocol.Encoding.GetBytes(appendFilename);
                body_len = 2 * Protocol.FDFS_PROTO_PKG_LEN_SIZE + appenderFilenameBytes.Length + fileSize;

                header = Protocol.PackHeader(Protocol.STORAGE_PROTO_CMD_APPEND_FILE, body_len, (byte)0);
                byte[] wholePkg = new byte[(int)(header.Length + body_len - fileSize)];
                Array.Copy(header, 0, wholePkg, 0, header.Length);
                offset = header.Length;

                hexLenBytes = Protocol.ConvertLongToBuff(appendFilename.Length);
                Array.Copy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.Length);
                offset += hexLenBytes.Length;

                hexLenBytes = Protocol.ConvertLongToBuff(fileSize);
                Array.Copy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.Length);
                offset += hexLenBytes.Length;

                var stream = storageSocket.GetStream();

                Array.Copy(appenderFilenameBytes, 0, wholePkg, offset, appenderFilenameBytes.Length);
                offset += appenderFilenameBytes.Length;

                stream.Write(wholePkg, 0, wholePkg.Length);
                var error = callback.Send(stream);
                if (error != 0)
                {
                    return error;
                }

                var pkgInfo = Protocol.PackPackage(stream, Protocol.STORAGE_PROTO_CMD_RESP, 0);
                error = pkgInfo.ErrorCode;
                if (error != 0)
                {
                    return error;
                }

                return 0;

            });
        }


        /// <summary>
        /// <summary>
        /// 上传文件
        /// </summary>
        /// <param name="storageServer"></param>
        /// <param name="cmd"></param>
        /// <param name="groupName"></param>
        /// <param name="masterFilename"></param>
        /// <param name="prefixName"></param>
        /// <param name="fileExtName"></param>
        /// <param name="fileSize"></param>
        /// <param name="callback"></param>
        /// <returns></returns>
        protected string[] Upload(StorageServer storageServer, byte cmd, string groupName, string masterFilename,
             string prefixName, string fileExtName, long fileSize, IUploadCallback callback)
        {
            return serverPool.Execute<string[]>(serverPool.GetSocketPool(string.Format("{0}:{1}", storageServer.IP, storageServer.Port)), null, delegate(PooledSocket storageSocket)
            {
                byte[] header;
                byte[] ext_name_bs;
                string new_group_name;
                string remote_filename;
                byte[] sizeBytes;
                byte[] hexLenBytes;
                byte[] masterFilenameBytes;
                bool bUploadSlave;
                int offset;
                long body_len;

                bUploadSlave = ((!string.IsNullOrEmpty(groupName)) && (!string.IsNullOrEmpty(masterFilename)) && (!string.IsNullOrEmpty(prefixName)));



                ext_name_bs = new byte[Protocol.FDFS_FILE_EXT_NAME_MAX_LEN];

                for (var i = 0; i < ext_name_bs.Length; i++)
                {
                    ext_name_bs[i] = (byte)0;
                }

                if (!string.IsNullOrEmpty(fileExtName))
                {
                    byte[] bs = Protocol.Encoding.GetBytes(fileExtName);
                    int ext_name_len = bs.Length;
                    if (ext_name_len > Protocol.FDFS_FILE_EXT_NAME_MAX_LEN)
                    {
                        ext_name_len = Protocol.FDFS_FILE_EXT_NAME_MAX_LEN;
                    }

                    Array.Copy(bs, 0, ext_name_bs, 0, ext_name_len);
                }

                if (bUploadSlave)
                {
                    masterFilenameBytes = Protocol.Encoding.GetBytes(masterFilename);

                    sizeBytes = new byte[2 * Protocol.FDFS_PROTO_PKG_LEN_SIZE];
                    body_len = sizeBytes.Length + Protocol.FDFS_FILE_PREFIX_MAX_LEN + Protocol.FDFS_FILE_EXT_NAME_MAX_LEN
                             + masterFilenameBytes.Length + fileSize;

                    hexLenBytes = Protocol.ConvertLongToBuff(masterFilename.Length);
                    Array.Copy(hexLenBytes, 0, sizeBytes, 0, hexLenBytes.Length);
                    offset = hexLenBytes.Length;
                }
                else
                {
                    masterFilenameBytes = null;
                    sizeBytes = new byte[1 + 1 * Protocol.FDFS_PROTO_PKG_LEN_SIZE];
                    body_len = sizeBytes.Length + Protocol.FDFS_FILE_EXT_NAME_MAX_LEN + fileSize;

                    sizeBytes[0] = (byte)storageServer.StorePath;
                    offset = 1;
                }

                hexLenBytes = Protocol.ConvertLongToBuff(fileSize);
                Array.Copy(hexLenBytes, 0, sizeBytes, offset, hexLenBytes.Length);

                var stream = storageSocket.GetStream();
                header = Protocol.PackHeader(cmd, body_len, (byte)0);
                byte[] wholePkg = new byte[(int)(header.Length + body_len - fileSize)];
                Array.Copy(header, 0, wholePkg, 0, header.Length);
                Array.Copy(sizeBytes, 0, wholePkg, header.Length, sizeBytes.Length);
                offset = header.Length + sizeBytes.Length;

                if (bUploadSlave)
                {
                    byte[] prefix_name_bs = new byte[Protocol.FDFS_FILE_PREFIX_MAX_LEN];
                    byte[] bs = Protocol.Encoding.GetBytes(prefixName);
                    int prefix_name_len = bs.Length;

                    for (var i = 0; i < prefix_name_bs.Length; i++)
                    {
                        prefix_name_bs[i] = (byte)0;
                    }

                    if (prefix_name_len > Protocol.FDFS_FILE_PREFIX_MAX_LEN)
                    {
                        prefix_name_len = Protocol.FDFS_FILE_PREFIX_MAX_LEN;
                    }
                    if (prefix_name_len > 0)
                    {
                        Array.Copy(bs, 0, prefix_name_bs, 0, prefix_name_len);
                    }

                    Array.Copy(prefix_name_bs, 0, wholePkg, offset, prefix_name_bs.Length);
                    offset += prefix_name_bs.Length;
                }

                Array.Copy(ext_name_bs, 0, wholePkg, offset, ext_name_bs.Length);
                offset += ext_name_bs.Length;

                if (bUploadSlave)
                {
                    Array.Copy(masterFilenameBytes, 0, wholePkg, offset, masterFilenameBytes.Length);
                    offset += masterFilenameBytes.Length;
                }

                stream.Write(wholePkg, 0, wholePkg.Length);

                if (callback.Send(stream) != 0)
                {
                    return null;
                }

                var pkgInfo = Protocol.PackPackage(stream, Protocol.STORAGE_PROTO_CMD_RESP, -1);

                if (pkgInfo.ErrorCode != 0)
                {
                    return null;
                }

                if (pkgInfo.Body.Length <= Protocol.FDFS_GROUP_NAME_MAX_LEN)
                {
                    throw new Exception("body length: " + pkgInfo.Body.Length + " <= " + Protocol.FDFS_GROUP_NAME_MAX_LEN);
                }

                new_group_name = Protocol.Encoding.GetString(pkgInfo.Body, 0, Protocol.FDFS_GROUP_NAME_MAX_LEN).Trim('\0');
                remote_filename = Protocol.Encoding.GetString(pkgInfo.Body, Protocol.FDFS_GROUP_NAME_MAX_LEN, pkgInfo.Body.Length - Protocol.FDFS_GROUP_NAME_MAX_LEN);
                var results = new string[2];
                results[0] = new_group_name;
                results[1] = remote_filename;


                return results;
            });
        }

        #endregion

        #region 删除文件

        public int Delete(StorageServer storageServer, string groupName, string remoteFilename)
        {
            return send_package(storageServer, Protocol.STORAGE_PROTO_CMD_DELETE_FILE, groupName, remoteFilename);
        }

        protected int send_package(StorageServer storageServer, byte cmd, string group_name, string remote_filename)
        {
            return serverPool.Execute<int>(serverPool.GetSocketPool(string.Format("{0}:{1}", storageServer.IP, storageServer.Port)), 0, delegate(PooledSocket storageSocket)
            {
                byte[] header;
                byte[] groupBytes;
                byte[] filenameBytes;
                byte[] bs;
                int groupLen;

                groupBytes = new byte[Protocol.FDFS_GROUP_NAME_MAX_LEN];
                bs = Protocol.Encoding.GetBytes(group_name);
                filenameBytes = Protocol.Encoding.GetBytes(remote_filename);

                for (var i = 0; i < groupBytes.Length; i++)
                {
                    groupBytes[i] = (byte)0;
                }

                if (bs.Length <= groupBytes.Length)
                {
                    groupLen = bs.Length;
                }
                else
                {
                    groupLen = groupBytes.Length;
                }
                Array.Copy(bs, 0, groupBytes, 0, groupLen);

                header = Protocol.PackHeader(cmd, groupBytes.Length + filenameBytes.Length, (byte)0);
                byte[] wholePkg = new byte[header.Length + groupBytes.Length + filenameBytes.Length];
                Array.Copy(header, 0, wholePkg, 0, header.Length);
                Array.Copy(groupBytes, 0, wholePkg, header.Length, groupBytes.Length);
                Array.Copy(filenameBytes, 0, wholePkg, header.Length + groupBytes.Length, filenameBytes.Length);
                var stream = storageSocket.GetStream();
                stream.Write(wholePkg, 0, wholePkg.Length);
                var pkgInfo = Protocol.PackPackage(stream, Protocol.STORAGE_PROTO_CMD_RESP, 0);

                return pkgInfo.ErrorCode;

            });
        }

        #endregion

        #region 下载文件

        public int Download(StorageServer storageServer, string group_name, string remote_filename, long file_offset, long download_bytes, IDownloadCallback callback)
        {
            return serverPool.Execute<int>(serverPool.GetSocketPool(string.Format("{0}:{1}", storageServer.IP, storageServer.Port)), 0, delegate(PooledSocket storageSocket)
            {
                byte[] header;
                byte[] bsOffset;
                byte[] bsDownBytes;
                byte[] groupBytes;
                byte[] filenameBytes;
                byte[] bs;
                int groupLen;

                bsOffset = Protocol.ConvertLongToBuff(file_offset);
                bsDownBytes = Protocol.ConvertLongToBuff(download_bytes);
                groupBytes = new byte[Protocol.FDFS_GROUP_NAME_MAX_LEN];
                bs = Protocol.Encoding.GetBytes(group_name);
                filenameBytes = Protocol.Encoding.GetBytes(remote_filename);

                for (var i = 0; i < groupBytes.Length; i++)
                {
                    groupBytes[i] = (byte)0;
                }

                if (bs.Length <= groupBytes.Length)
                {
                    groupLen = bs.Length;
                }
                else
                {
                    groupLen = groupBytes.Length;
                }

                Array.Copy(bs, 0, groupBytes, 0, groupLen);

                header = Protocol.PackHeader(Protocol.STORAGE_PROTO_CMD_DOWNLOAD_FILE,
                     bsOffset.Length + bsDownBytes.Length + groupBytes.Length + filenameBytes.Length, (byte)0);
                byte[] wholePkg = new byte[header.Length + bsOffset.Length + bsDownBytes.Length + groupBytes.Length + filenameBytes.Length];
                Array.Copy(header, 0, wholePkg, 0, header.Length);
                Array.Copy(bsOffset, 0, wholePkg, header.Length, bsOffset.Length);
                Array.Copy(bsDownBytes, 0, wholePkg, header.Length + bsOffset.Length, bsDownBytes.Length);
                Array.Copy(groupBytes, 0, wholePkg, header.Length + bsOffset.Length + bsDownBytes.Length, groupBytes.Length);
                Array.Copy(filenameBytes, 0, wholePkg, header.Length + bsOffset.Length + bsDownBytes.Length + groupBytes.Length, filenameBytes.Length);
                var stream = storageSocket.GetStream();
                stream.Write(wholePkg, 0, wholePkg.Length);
                var receivedheader = Protocol.PackHeader(stream, Protocol.STORAGE_PROTO_CMD_RESP, -1);
                if (receivedheader.ErrorCode != 0)
                {
                    return receivedheader.ErrorCode;
                }

                byte[] buff = new byte[2 * 1024];
                long remainBytes = receivedheader.BodyLength;
                int bytes;


                while (remainBytes > 0)
                {
                    if ((bytes = stream.Read(buff, 0, remainBytes > buff.Length ? buff.Length : (int)remainBytes)) < 0)
                    {
                        throw new IOException("recv package size " + (receivedheader.BodyLength - remainBytes) + " != " + receivedheader.BodyLength);
                    }

                    var result = callback.Recieve(receivedheader.BodyLength, buff, bytes);
                    if (result != 0)
                    {
                        return result;
                    }

                    remainBytes -= bytes;
                }

                return 0;

            });
        }

        #endregion
    }
}
